{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "316b4630",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI, OpenAI\n",
    "\n",
    "openai_api_key = \"EMPTY\"\n",
    "openai_api_base = \"http://127.0.0.1:1234/v1\"\n",
    "model = ChatOpenAI(\n",
    "    openai_api_key=openai_api_key,\n",
    "    openai_api_base=openai_api_base,\n",
    "    temperature=0.1,\n",
    ")\n",
    "llm = model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "70971ebb",
   "metadata": {},
   "outputs": [],
   "source": [
    "promptTemplate = \"\"\"尽可能的帮助用户回答任何问题。\n",
    "\n",
    "您可以使用以下工具来帮忙解决问题，如果已经知道了答案，也可以直接回答：\n",
    "\n",
    "{tools}\n",
    "\n",
    "回复格式说明\n",
    "----------------------------\n",
    "\n",
    "回复我时，请以以下两种格式之一输出回复：\n",
    "\n",
    "选项 1：如果您希望人类使用工具，请使用此选项。\n",
    "采用以下JSON模式格式化的回复内容：\n",
    "\n",
    "```json\n",
    "{{\n",
    "    \"reason\": string, \\\\ 叙述使用工具的原因\n",
    "    \"action\": string, \\\\ 要使用的工具。 必须是 {tool_names} 之一\n",
    "    \"action_input\": string \\\\ 工具的输入\n",
    "}}\n",
    "````\n",
    "\n",
    "选项2：如果您认为你已经有答案或者已经通过使用工具找到了答案，想直接对人类做出反应，请使用此选项。 采用以下JSON模式格式化的回复内容：\n",
    "\n",
    "```json\n",
    "{{\n",
    "  \"action\": \"Final Answer\",\n",
    "  \"answer\": string \\\\最终答复问题的答案放到这里！\n",
    "}}\n",
    "````\n",
    "\n",
    "用户的输入\n",
    "--------------------\n",
    "这是用户的输入（请记住通过单个选项，以JSON模式格式化的回复内容，不要回复其他内容）：\n",
    "\n",
    "{input}\n",
    "\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c747bd84",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"你是非常强大的助手，你可以使用各种工具来完成人类交给的问题和任务。\",\n",
    "        ),\n",
    "        (\"user\", promptTemplate),\n",
    "        MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "de9f6e7f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"TOOL RESPONSE: \\n---------------------\\n{observation}\\n\\nUSER'S INPUT\\n--------------------\\n\\nOkay, so what is the response to my last comment? If using information obtained from the tools you must mention it explicitly without mentioning the tool names - I have forgotten all TOOL RESPONSES! Remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else - even if you just want to respond to the user. Do NOT respond with anything except a JSON snippet no matter what!\""
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents.json_chat.prompt import TEMPLATE_TOOL_RESPONSE\n",
    "\n",
    "TEMPLATE_TOOL_RESPONSE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "111dd717",
   "metadata": {},
   "outputs": [],
   "source": [
    "TEMPLATE_TOOL_RESPONSE = \"\"\"工具响应：\n",
    "---------------------\n",
    "{observation}\n",
    "\n",
    "用户的输入：\n",
    "---------------------\n",
    "请根据工具的响应判断，是否能够回答问题：\n",
    "\n",
    "{input}\n",
    "\n",
    "请根据工具响应的内容，思考接下来回复。回复格式严格按照前面所说的2种JSON回复格式，选择其中1种进行回复。请记住通过单个选项，以JSON模式格式化的回复内容，不要回复其他内容。\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "047f8796",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n",
    "def format_log_to_messages(\n",
    "    query,\n",
    "    intermediate_steps,\n",
    "    template_tool_response,\n",
    "):\n",
    "    \"\"\"构建思考过程。\"\"\"\n",
    "    thoughts: List[BaseMessage] = []\n",
    "    for action, observation in intermediate_steps:\n",
    "        thoughts.append(AIMessage(content=action.log))\n",
    "        human_message = HumanMessage(\n",
    "            content=template_tool_response.format(input=query,observation=observation)\n",
    "        )\n",
    "        thoughts.append(human_message)\n",
    "    return thoughts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "c62d3a40",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents import tool\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_word_length(word: str) -> int:\n",
    "    \"\"\"Returns the length of a word.\"\"\"\n",
    "    return len(word)\n",
    "\n",
    "\n",
    "get_word_length.invoke(\"abc\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2dbb3b5a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'description': '多云', 'temperature': '24'}"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "@tool\n",
    "def get_weather(location):\n",
    "    \"\"\"根据城市获取天气数据\"\"\"\n",
    "    api_key = \"SKcA5FGgmLvN7faJi\"\n",
    "    url = f\"https://api.seniverse.com/v3/weather/now.json?key={api_key}&location={location}&language=zh-Hans&unit=c\"\n",
    "    response = requests.get(url)\n",
    "    #print(location)\n",
    "    if response.status_code == 200:\n",
    "        data = response.json()\n",
    "        #print(data)\n",
    "        weather = {\n",
    "            'description':data['results'][0][\"now\"][\"text\"],\n",
    "            'temperature':data['results'][0][\"now\"][\"temperature\"]\n",
    "        }\n",
    "        return weather\n",
    "    else:\n",
    "        raise Exception(f\"失败接收天气信息：{response.status_code}\")\n",
    "            \n",
    "get_weather.invoke(\"广州\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3dc264d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [get_word_length,get_weather]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "4ce1a73d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptTemplate(input_variables=['agent_scratchpad', 'input'], input_types={'agent_scratchpad': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]]}, partial_variables={'tools': 'get_word_length: get_word_length(word: str) -> int - Returns the length of a word.\\nget_weather: get_weather(location) - 根据城市获取天气数据', 'tool_names': 'get_word_length, get_weather'}, messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='你是非常强大的助手，你可以使用各种工具来完成人类交给的问题和任务。')), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input', 'tool_names', 'tools'], template='尽可能的帮助用户回答任何问题。\\n\\n您可以使用以下工具来帮忙解决问题，如果已经知道了答案，也可以直接回答：\\n\\n{tools}\\n\\n回复格式说明\\n----------------------------\\n\\n回复我时，请以以下两种格式之一输出回复：\\n\\n选项 1：如果您希望人类使用工具，请使用此选项。\\n采用以下JSON模式格式化的回复内容：\\n\\n```json\\n{{\\n    \"reason\": string, \\\\ 叙述使用工具的原因\\n    \"action\": string, \\\\ 要使用的工具。 必须是 {tool_names} 之一\\n    \"action_input\": string \\\\ 工具的输入\\n}}\\n````\\n\\n选项2：如果您认为你已经有答案或者已经通过使用工具找到了答案，想直接对人类做出反应，请使用此选项。 采用以下JSON模式格式化的回复内容：\\n\\n```json\\n{{\\n  \"action\": \"Final Answer\",\\n  \"answer\": string \\\\最终答复问题的答案放到这里！\\n}}\\n````\\n\\n用户的输入\\n--------------------\\n这是用户的输入（请记住通过单个选项，以JSON模式格式化的回复内容，不要回复其他内容）：\\n\\n{input}\\n\\n')), MessagesPlaceholder(variable_name='agent_scratchpad')])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.tools.render import  render_text_description\n",
    "\n",
    "prompt = prompt.partial(\n",
    "        tools=render_text_description(list(tools)),\n",
    "        tool_names=\", \".join([t.name for t in tools]),\n",
    ")\n",
    "prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "e56a3f0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents.agent import AgentOutputParser\n",
    "from langchain_core.output_parsers.json import parse_json_markdown\n",
    "from langchain_core.exceptions import OutputParserException\n",
    "from langchain_core.agents import AgentAction, AgentFinish\n",
    "class JSONAgentOutputParser(AgentOutputParser):\n",
    "\n",
    "    def parse(self, text):\n",
    "        try:\n",
    "            response = parse_json_markdown(text)\n",
    "            if isinstance(response, list):\n",
    "                # 经常忽略发出单个动作的指令\n",
    "                logger.warning(\"Got multiple action responses: %s\", response)\n",
    "                response = response[0]\n",
    "            if response[\"action\"] == \"Final Answer\":\n",
    "                return AgentFinish({\"output\": response[\"answer\"]}, text)\n",
    "            else:\n",
    "                return AgentAction(\n",
    "                    response[\"action\"], response.get(\"action_input\", {}), text\n",
    "                )\n",
    "        except Exception as e:\n",
    "            raise OutputParserException(f\"Could not parse LLM output: {text}\") from e\n",
    "\n",
    "    @property\n",
    "    def _type(self) -> str:\n",
    "        return \"json-agent\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "d7b7cb3c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from langchain.agents.output_parsers import JSONAgentOutputParser\n",
    "from langchain_core.runnables import Runnable, RunnablePassthrough\n",
    "\n",
    "\n",
    "agent = (\n",
    "        RunnablePassthrough.assign(\n",
    "            agent_scratchpad=lambda x: format_log_to_messages(\n",
    "                x[\"input\"],\n",
    "                x[\"intermediate_steps\"], \n",
    "                template_tool_response=TEMPLATE_TOOL_RESPONSE\n",
    "            )\n",
    "        )\n",
    "        | prompt\n",
    "        | llm\n",
    "        | JSONAgentOutputParser()\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "e2d2ad17",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m```json\n",
      "{ \n",
      "   \"reason\": \"需要获取实时天气信息\", \n",
      "   \"action\": \"get_weather\", \n",
      "   \"action_input\": \"广州\" \n",
      "}\n",
      "```\u001b[0m\u001b[33;1m\u001b[1;3m{'description': '多云', 'temperature': '24'}\u001b[0m\u001b[32;1m\u001b[1;3m```json\n",
      "{ \n",
      "    \"action\": \"Final Answer\", \n",
      "    \"answer\": \"广州的天气是多云，温度为24度。\"\n",
      "}\n",
      "```\n",
      "好的，我已经回答了用户的问题。\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '广州的天气如何？', 'output': '广州的天气是多云，温度为24度。'}"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
    "agent_executor.invoke({\"input\": \"广州的天气如何？\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "0046c470",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True,return_intermediate_steps=True,)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "5bf765cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m```json\n",
      "{ \n",
      "   \"reason\": \"用户询问了广州的天气情况, 需要使用get_weather工具获取\",\n",
      "   \"action\": \"get_weather\",\n",
      "   \"action_input\": \"广州\"\n",
      "}\n",
      "```\u001b[0m\u001b[33;1m\u001b[1;3m{'description': '多云', 'temperature': '24'}\u001b[0m\u001b[32;1m\u001b[1;3m```json\n",
      "{ \n",
      "    \"action\": \"Final Answer\",\n",
      "    \"answer\": \"广州的天气是多云，温度为24度。\"\n",
      "}\n",
      "```\n",
      "<|startoftext|>\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '广州的天气如何？',\n",
       " 'output': '广州的天气是多云，温度为24度。',\n",
       " 'intermediate_steps': [(AgentAction(tool='get_weather', tool_input='广州', log='```json\\n{ \\n   \"reason\": \"用户询问了广州的天气情况, 需要使用get_weather工具获取\",\\n   \"action\": \"get_weather\",\\n   \"action_input\": \"广州\"\\n}\\n```'),\n",
       "   {'description': '多云', 'temperature': '24'})]}"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor.invoke({\"input\": \"广州的天气如何？\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "60374c8e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m```json\n",
      "{ \n",
      "   \"reason\": \"用户询问了广州的天气情况, 需要使用get_weather工具获取\",\n",
      "   \"action\": \"get_weather\",\n",
      "   \"action_input\": \"广州\"\n",
      "}\n",
      "```\u001b[0m\u001b[33;1m\u001b[1;3m{'description': '多云', 'temperature': '24'}\u001b[0m\u001b[32;1m\u001b[1;3m```json\n",
      "{ \n",
      "    \"action\": \"Final Answer\",\n",
      "    \"answer\": \"广州的天气是多云，温度为24度。\"\n",
      "}\n",
      "```\n",
      "<|startoftext|>\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '广州的天气如何？', 'output': '广州的天气是多云，温度为24度。'}"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True,max_iterations=2)\n",
    "agent_executor.invoke({\"input\": \"广州的天气如何？\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "b99fed27",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m```json\n",
      "{ \n",
      "   \"reason\": \"用户询问了广州的天气情况, 需要使用get_weather工具获取\",\n",
      "   \"action\": \"get_weather\",\n",
      "   \"action_input\": \"广州\"\n",
      "}\n",
      "```\u001b[0m\u001b[33;1m\u001b[1;3m{'description': '多云', 'temperature': '24'}\u001b[0m\u001b[32;1m\u001b[1;3m\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '广州的天气如何？',\n",
       " 'output': 'Agent stopped due to iteration limit or time limit.'}"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_execution_time=1,)\n",
    "agent_executor.invoke({\"input\": \"广州的天气如何？\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "976f198d",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
